import { gfx, Mesh, primitives, Vec2, Vec3 } from "cc";

export class MeshUtils {
    static getPadding (length: number, align: number): number {
        if (align > 0) {
            const remainder = length % align;
            if (remainder !== 0) {
                const padding = align - remainder;
                return padding;
            }
        }
    
        return 0;
    }

    static createDynamicMesh(geometries: primitives.IDynamicGeometry[], out?: Mesh): Mesh {
        const attributes: gfx.Attribute[] = [];
        let stream = 0;

        const vertexBundles: Mesh.IVertexBundle[] = [];
        const primitives: Mesh.ISubMesh[] = [];
        let dataSize = 0;

        let maxSubMeshVertices = 0;
        let maxSubMeshIndices = 0;
        for (let i = 0; i < geometries.length; i++) {
            let geometry: primitives.IDynamicGeometry = geometries[i];   
            maxSubMeshVertices += geometry.positions.length;      
            maxSubMeshIndices += (geometry.indices16 ? geometry.indices16.length : geometry.indices32!.length);

            if (geometry.positions.length > 0) {
                attributes.push(new gfx.Attribute(gfx.AttributeName.ATTR_POSITION, gfx.Format.RGB32F, false, stream++, false, 0));
            }

            if (geometry.normals && geometry.normals.length > 0) {
                attributes.push(new gfx.Attribute(gfx.AttributeName.ATTR_NORMAL, gfx.Format.RGB32F, false, stream++, false, 0));
            }

            if (geometry.uvs && geometry.uvs.length > 0) {
                attributes.push(new gfx.Attribute(gfx.AttributeName.ATTR_TEX_COORD, gfx.Format.RG32F, false, stream++, false, 0));
            }

            if (geometry.tangents && geometry.tangents.length > 0) {
                attributes.push(new gfx.Attribute(gfx.AttributeName.ATTR_TANGENT, gfx.Format.RGBA32F, false, stream++, false, 0));
            }

            if (geometry.colors && geometry.colors.length > 0) {
                attributes.push(new gfx.Attribute(gfx.AttributeName.ATTR_COLOR, gfx.Format.RGBA32F, false, stream++, false, 0));
            }

            if (geometry.customAttributes) {
                for (let k = 0; k < geometry.customAttributes.length; k++) {
                    const ca = geometry.customAttributes[k];
                    const attr = new gfx.Attribute();
                    attr.copy(ca.attr);
                    attr.stream = stream++;
                    attributes.push(attr);
                }
            }

            const primitive: Mesh.ISubMesh = {
                vertexBundelIndices: [],
                primitiveMode: geometry.primitiveMode || gfx.PrimitiveMode.TRIANGLE_LIST,
            };

            // add vertex buffers
            for (const attr of attributes) {
                const formatInfo = gfx.FormatInfos[attr.format];
                const vertexBufferSize = geometry.positions.length * 3 * formatInfo.size;

                const vertexView: Mesh.IBufferView = {
                    offset: dataSize,
                    length: vertexBufferSize,
                    count: 0,
                    stride: formatInfo.size,
                };

                const vertexBundle: Mesh.IVertexBundle = {
                    view: vertexView,
                    attributes: [attr],
                };

                const vertexBundleIndex = vertexBundles.length;
                primitive.vertexBundelIndices.push(vertexBundleIndex);
                vertexBundles.push(vertexBundle);
                dataSize += vertexBufferSize;
            }

            // add index buffer
            let stride = 0;
            if (geometry.indices16 && geometry.indices16.length > 0) {
                stride = 2;
            } else if (geometry.indices32 && geometry.indices32.length > 0) {
                stride = 4;
            }

            if (stride > 0) {
                dataSize += this.getPadding(dataSize, stride);
                const indexBufferSize = stride * (geometry.indices16 ? geometry.indices16.length : geometry.indices32!.length);

                const indexView: Mesh.IBufferView = {
                    offset: dataSize,
                    length: indexBufferSize,
                    count: 0,
                    stride,
                };

                primitive.indexView = indexView;
                dataSize += indexBufferSize;
            }

            primitives.push(primitive);
        }

        const dynamicInfo: Mesh.IDynamicInfo = {
            maxSubMeshes: geometries.length,
            maxSubMeshVertices,
            maxSubMeshIndices,
        };

        const dynamicStruct: Mesh.IDynamicStruct = {
            info: dynamicInfo,
            bounds: [],
        };
        dynamicStruct.bounds.length = geometries.length;

        const meshStruct: Mesh.IStruct = {
            vertexBundles,
            primitives,
            dynamic: dynamicStruct,
        };

        const createInfo: Mesh.ICreateInfo = {
            struct: meshStruct,
            data: new Uint8Array(dataSize),
        };

        if (!out) {
            out = new Mesh();
        }

        // 原生模式下，reset后dynamic信息丢失
        out.reset(createInfo);
        out.initialize();
        for(let i=0; i<geometries.length; i++) {
            out.updateSubMesh(i, geometries[i]);
        }
        return out;
    }

    /**
     * 计算切线
     * @param positions 
     * @param normals 
     * @param uvs 
     * @param indices 
     * @param tangents 
     * @returns 
     */
    public static calculateTangents(positions: Float32Array, normals: Float32Array, uvs: Float32Array, indices: Uint16Array | Uint32Array): Float32Array {
    let tangents = new Float32Array(positions.length / 3 * 4);
    let tan1 = new Float32Array(positions.length);
    let tan2 = new Float32Array(positions.length);

    const handleTriangle = (a: number, b: number, c: number) => {
        // 获取顶点位置
        const aX = positions[a * 3];
        const aY = positions[a * 3 + 1];
        const aZ = positions[a * 3 + 2];
        
        const bX = positions[b * 3];
        const bY = positions[b * 3 + 1];
        const bZ = positions[b * 3 + 2];
        
        const cX = positions[c * 3];
        const cY = positions[c * 3 + 1];
        const cZ = positions[c * 3 + 2];

        // 获取UV坐标
        const uvAX = uvs[a * 2];
        const uvAY = uvs[a * 2 + 1];
        
        const uvBX = uvs[b * 2];
        const uvBY = uvs[b * 2 + 1];
        
        const uvCX = uvs[c * 2];
        const uvCY = uvs[c * 2 + 1];

        // 计算位置差值
        const vBX = bX - aX;
        const vBY = bY - aY;
        const vBZ = bZ - aZ;
        
        const vCX = cX - aX;
        const vCY = cY - aY;
        const vCZ = cZ - aZ;

        // 计算UV差值
        const uvBX_diff = uvBX - uvAX;
        const uvBY_diff = uvBY - uvAY;
        
        const uvCX_diff = uvCX - uvAX;
        const uvCY_diff = uvCY - uvAY;

        // 计算r
        const r = 1.0 / (uvBX_diff * uvCY_diff - uvCX_diff * uvBY_diff);
        
        // 计算sdir和tdir
        const sdirX = (uvCY_diff * vBX - uvBY_diff * vCX) * r;
        const sdirY = (uvCY_diff * vBY - uvBY_diff * vCY) * r;
        const sdirZ = (uvCY_diff * vBZ - uvBY_diff * vCZ) * r;
        
        const tdirX = (uvBX_diff * vCX - uvCX_diff * vBX) * r;
        const tdirY = (uvBX_diff * vCY - uvCX_diff * vBY) * r;
        const tdirZ = (uvBX_diff * vCZ - uvCX_diff * vBZ) * r;

        // 累加到tan1和tan2
        tan1[a * 3] += sdirX;
        tan1[a * 3 + 1] += sdirY;
        tan1[a * 3 + 2] += sdirZ;

        tan1[b * 3] += sdirX;
        tan1[b * 3 + 1] += sdirY;
        tan1[b * 3 + 2] += sdirZ;

        tan1[c * 3] += sdirX;
        tan1[c * 3 + 1] += sdirY;
        tan1[c * 3 + 2] += sdirZ;

        tan2[a * 3] += tdirX;
        tan2[a * 3 + 1] += tdirY;
        tan2[a * 3 + 2] += tdirZ;

        tan2[b * 3] += tdirX;
        tan2[b * 3 + 1] += tdirY;
        tan2[b * 3 + 2] += tdirZ;

        tan2[c * 3] += tdirX;
        tan2[c * 3 + 1] += tdirY;
        tan2[c * 3 + 2] += tdirZ;
    };

    // 处理所有三角形
    for (let i = 0; i < indices.length; i += 3) {
        const a = indices[i];
        const b = indices[i + 1];
        const c = indices[i + 2];
        handleTriangle(a, b, c);
    }

    // 计算最终的切线
    const pointSize = positions.length / 3;
    for (let i = 0; i < pointSize; i++) {
        // 获取法线
        const nX = normals[i * 3];
        const nY = normals[i * 3 + 1];
        const nZ = normals[i * 3 + 2];
        
        // 获取tan1
        const tX = tan1[i * 3];
        const tY = tan1[i * 3 + 1];
        const tZ = tan1[i * 3 + 2];

        // Gram-Schmidt 正交化
        // 计算点积
        const dotProduct = nX * tX + nY * tY + nZ * tZ;
        
        // 计算投影
        const projX = nX * dotProduct;
        const projY = nY * dotProduct;
        const projZ = nZ * dotProduct;
        
        // 切线 = t - 投影
        let tangentX = tX - projX;
        let tangentY = tY - projY;
        let tangentZ = tZ - projZ;
        
        // 归一化切线
        const length = Math.sqrt(tangentX * tangentX + tangentY * tangentY + tangentZ * tangentZ);
        if (length > 0) {
            tangentX /= length;
            tangentY /= length;
            tangentZ /= length;
        }
        
        // 存储切线
        tangents[i * 4] = tangentX;
        tangents[i * 4 + 1] = tangentY;
        tangents[i * 4 + 2] = tangentZ;

        // 计算手性
        // 计算叉乘 n × t
        const crossX = nY * tZ - nZ * tY;
        const crossY = nZ * tX - nX * tZ;
        const crossZ = nX * tY - nY * tX;
        
        // 与tan2点积，确定手性
        const tan2X = tan2[i * 3];
        const tan2Y = tan2[i * 3 + 1];
        const tan2Z = tan2[i * 3 + 2];
        const handedness = (crossX * tan2X + crossY * tan2Y + crossZ * tan2Z) < 0.0 ? -1.0 : 1.0;
        tangents[i * 4 + 3] = handedness;
    }

    return tangents;
}
}